/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Renderer.h"
#include <math/utl.h>
#include "Image.h"
#include <win32/win32.h>

namespace liba
{
namespace lib2d
{
namespace hard
{
namespace directx8
{

static HRESULT hr;

template<class T>
int GlobalRelease(T ** object)
{
	int ref_count = 0;
	if( *object )
		ref_count = (*object)->Release();
	(*object) = 0;
	return ref_count;
}

Renderer::Renderer( LPDIRECT3D8 direct3d, int adapter_number )
:	wnd( 0 ),
	width( 0 ),
	height( 0 )
{}

bool Renderer::set_mode(  WindowImpl * window_impl, int resolution_x, int resolution_y, bool fullscreen )
{
	fullscreen = false;
	memset( &present_parameters, 0, sizeof(D3DPRESENT_PARAMETERS) );

	present_parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;//D3DSWAPEFFECT_COPY_VSYNC;//D3DSWAPEFFECT_FLIP;
	present_parameters.BackBufferFormat = parameters.color_format;
	present_parameters.BackBufferCount = 1;
	present_parameters.hDeviceWindow = window_impl->get_wnd();
	present_parameters.EnableAutoDepthStencil = false;

	window_styles = GetWindowLong( present_parameters.hDeviceWindow, GWL_STYLE );
	if( fullscreen )
	{
		present_parameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //D3DPRESENT_INTERVAL_ONE
		present_parameters.Windowed = false;
		present_parameters.BackBufferWidth = resolution_x;
		present_parameters.BackBufferHeight = resolution_y;
		MoveWindow( present_parameters.hDeviceWindow, 0, 0, present_parameters.BackBufferWidth, present_parameters.BackBufferHeight, true );
		SetWindowLong( present_parameters.hDeviceWindow, GWL_STYLE, WS_POPUP );
	}
	else
	{
		D3DDISPLAYMODE display_mode;
		direct3d->GetAdapterDisplayMode( 0, &display_mode );
		present_parameters.BackBufferFormat = display_mode.Format;
		present_parameters.Windowed = true;
		present_parameters.BackBufferWidth = 0;
		present_parameters.BackBufferHeight = 0;

		width = resolution_x;
		height = resolution_y;

		//calculating window borders size
		RECT win_rect;
		GetWindowRect( present_parameters.hDeviceWindow, &win_rect );
		RECT rect;
		GetClientRect( present_parameters.hDeviceWindow, &rect );
		int new_width = width +	(win_rect.right - win_rect.left) - (rect.right - rect.left);
		int new_height = height + (win_rect.bottom - win_rect.top) - (rect.bottom - rect.top);

		//centering window
		int scr_x = GetSystemMetrics( SM_CXSCREEN );
		int scr_y = GetSystemMetrics( SM_CYSCREEN );
		int shi_x = (scr_x - new_width)/2;
		int shi_y = (scr_y - new_height)/2;
		if( shi_x < 0 )	shi_x = 0;
		if( shi_y < 0 )	shi_y = 0;
		MoveWindow( present_parameters.hDeviceWindow, shi_x, shi_y, new_width, new_height, true );

	}
	SetWindowPos( present_parameters.hDeviceWindow, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED );
	GetClientRect( present_parameters.hDeviceWindow, &current_rect );

	if( try_hardware_vp )
	{
		logs::log() << "Hardware::set_mode_internal() try_hardware_vp\n";
		hr = direct3d->CreateDevice( adapter_number, D3DDEVTYPE_HAL, present_parameters.hDeviceWindow, 
			D3DCREATE_HARDWARE_VERTEXPROCESSING,
			&present_parameters, &device );
	}
	if( !device )
	{
		logs::log() << "Hardware::set_mode_internal() try_software_vp\n";
		hr = direct3d->CreateDevice( adapter_number, D3DDEVTYPE_HAL, present_parameters.hDeviceWindow, 
			D3DCREATE_SOFTWARE_VERTEXPROCESSING,
			&present_parameters, &device );
		if( !device )
		{
			logs::log() << logs::error << "Hardware::set_mode_internal() CreateDevice failed\n";
			return false;
		}
	}
	device_memory = 0;
	report_device_memory();
///	restore_resources();
///	report_device_memory();

	logs::log() << "Mode set\n";
	push_clip_rect( Rect( 0, 0, width, height ) );
	return true;
}

void Renderer::report_device_memory()
{
	int new_device_memory = device->GetAvailableTextureMem();
	if( device_memory != new_device_memory )
	{
		device_memory = new_device_memory;
		logs::log() << "Device (Video+AGP) Memory Available: " << double(device_memory)/1024/1024 << "Mb\n";
	}
}

void Renderer::stop_mode()
{
	logs::log() << "Hardware::stop_mode()\n";
///	lose_resources();
	if( device )
	{
		GlobalRelease( &device );
	}
	SetWindowLong( present_parameters.hDeviceWindow, GWL_STYLE, window_styles );
	SetWindowPos( present_parameters.hDeviceWindow, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED );
	pop_clip_rect();
}

void Renderer::fill( const Rect & rect, const ColorPacked & color)
{
	Rect clipped_rect( rect );
	clipped_rect.shift( shifts.top() );
	clipped_rect.intersect( clip_rects.back() );

	int left = math::clamp<int>( clipped_rect.x, 0, width );
	int right = math::clamp<int>( clipped_rect.x + clipped_rect.width, 0, width );
	int top = math::clamp<int>( clipped_rect.y, 0, height );
	int bottom = math::clamp<int>( clipped_rect.y + clipped_rect.height, 0, height );

	if( left >= right )
		return;
	if( top >= bottom )
		return;

	ColorPacked * total_start = canvas->begin() + canvas->offset( left, top );
	ColorPacked * total_end = canvas->begin() + canvas->offset( left, bottom );
	do // We paint at least 1x1 - this helps optimizer very much
	{
		ColorPacked * line_start = total_start;
		ColorPacked * line_end = line_start + right - left;
		do
		{
			*line_start++ = color;
		}while( line_start != line_end );
		total_start += canvas->cx();
	}while( total_start != total_end );
}

void Renderer::flip()
{
	POINT client_p = { 0, 0 };
	ClientToScreen( wnd, &client_p );
	RECT rect;
	GetWindowRect( wnd, &rect );
	// Calculate the coordinates of client area relative to window area
	int relative_x = client_p.x - rect.left;
	int relative_y = client_p.y - rect.top;
 
	HDC dc = GetWindowDC( wnd );
	int cx = canvas->cx();
	int cy = canvas->cy();
	BITMAPINFO bmpInfo;
	BITMAPINFOHEADER BM_Info_Header = 
	{
		sizeof(BITMAPINFOHEADER),
		cx, -cy,
		1,
		32,
		BI_RGB,
		cx * cy * 4,
		0,
		0,
		0,
		0
	};
	bmpInfo.bmiHeader = BM_Info_Header;

	StretchDIBits( dc,
		relative_x, relative_y, cx, cy,
		0, 0, cx, cy,
		canvas->begin() + canvas->offset(0,0), &bmpInfo, DIB_RGB_COLORS, SRCCOPY );

	ReleaseDC( wnd, dc ); dc = NULL;
}

int Renderer::get_height()const
{
	return height;
}

int Renderer::get_width()const
{
	return width;
}

void Renderer::start_scene( const Rect & rect )
{
	push_clip_rect( rect );
}

void Renderer::finish_scene()
{
	pop_clip_rect();
}

hard::Image * Renderer::create_image( int sx, int sy, pictureformat::Format format )
{
	if( format != pictureformat::A8R8G8B8 )
		return 0;

	return new Image( this, sx, sy );
}

void Renderer::draw_rect( const Rect & rect, const ColorPacked & color )
{
	//TODO - use RectDrawer
}

void Renderer::draw_line( int x1, int y1, int x2, int y2, const ColorPacked & color )
{
	//TODO - use LineDrawer
}

void Renderer::set_gamma( int gamma )
{}

void Renderer::blit( Image * image, const Vector2 & dst, const Rect & rect, int alpha )
{
	Vector2 dest = dst+shifts.top();
	ColorPacked* dst_begin;//pointer to begin of destination image
	ColorPacked* src_begin;//ColorPacked* pointer to begin of source image
	unsigned width;//width - width
	unsigned height;//height - height
	unsigned dst_pitch = canvas->cx();
	unsigned src_pitch = image->width;
	Rect tmp(dest.x(),dest.y(),rect.width,rect.height);
	tmp.intersect(clip_rects.back());
	tmp.x += 0.5;
	tmp.y += 0.5;
	tmp.width += 0.5;
	tmp.height += 0.5;
	width = (int)tmp.width;
	height =  (int)tmp.height;
	
	dst_begin = canvas->begin()+canvas->offset((int)tmp.x,(int)tmp.y);
	src_begin = image->canvas.begin()+image->canvas.offset((int)(tmp.x-dest.x()+rect.x),(int)(tmp.y-dest.y()+rect.y));


	for(unsigned i=0;i<height;i++){
		for(unsigned j=0;j<width;j++){
			if( src_begin[j].pack != 0xffff00ff )
			{
				dst_begin[j].table_bilinear( src_begin[j], src_begin[j].a);
//				dst_begin[j] = src_begin[j];
			}
		}
		dst_begin += dst_pitch;
		src_begin += src_pitch;
	}
}

void Renderer::fill_image( Image * image, const Rect & dst_rect, const Rect & src_rect, int alpha )
{
/*	push_clip_rect( dst_rect );
	int x = dst_rect.x;
	while( x < dst_rect.get_right() )
	{
		int y = dst_rect.y;
		while( y < dst_rect.get_bottom() )
		{
			blit( image, Vector2( x, y ), src_rect, alpha );
			y += src_rect.height;
		}
		x += src_rect.width;
	}
	pop_clip_rect();
	*/

	Vector2 dest = Vector2( dst_rect.x, dst_rect.y )+shifts.top();
	ColorPacked* dst_begin;//pointer to begin of destination image
	ColorPacked* src_begin;//ColorPacked* pointer to begin of source image
	unsigned width = dst_rect.width;
	unsigned height = dst_rect.height;

	double dx = (double)src_rect.width / width;
	double dy = (double)src_rect.height / height;

	unsigned dst_pitch = canvas->cx();
	unsigned src_pitch = image->width;
	Rect tmp(dest.x(), dest.y(), dst_rect.width, dst_rect.height);
	tmp.intersect(clip_rects.back());
	tmp.x += 0.5;
	tmp.y += 0.5;
	tmp.width += 0.5;
	tmp.height += 0.5;
	if( tmp.width < 0 || tmp.height < 0 )
		return;
	width = (int)tmp.width;
	height =  (int)tmp.height;

	dst_begin = canvas->begin()+canvas->offset((int)tmp.x,(int)tmp.y);
	src_begin = image->canvas.begin()+image->canvas.offset((int)((tmp.x-dest.x())*dx+src_rect.x),(int)((tmp.y-dest.y())*dy+src_rect.y));

	ColorPacked* src_beg = src_begin;

	double y = 0;
	for( int y1=0; y1<height; ++y1 )
	{
		double x = 0;
		for( int x1 = 0; x1<width; ++x1 )
		{
				dst_begin[x1] = src_begin[int(x)];
				x += dx;
		}
		y += dy;
		dst_begin += dst_pitch;
		src_begin = src_beg + src_pitch * int( y );
	}
}

} //namespace directx8
} //namespace hard
} //namespace lib2d
} //namespace liba

